// Fluxions 3D Engine
// glut_template.cpp
// Copyright (C) 2000-2011 Jonathan Metzgar
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include "fluxions/opengl.hpp"
#include "fluxions/glut_template.hpp"


GlutTemplate GLUT;


namespace GlutTemplatePrivate
{
	const char *g_windowTitle = "GLUT Template Application";
	int g_displayMode = GLUT_DOUBLE | GLUT_STENCIL | GLUT_RGBA | GLUT_DEPTH;
	double g_Fps = 0;
	double g_aspectRatio = 1.333;
	int g_screenWidth = 720;
	int g_screenHeight = 480;
	int g_mouseX = 0;
	int g_mouseY = 0;
	int g_mouseLastX = 0;
	int g_mouseLastY = 0;
	int g_mouseButtons = 0;
	int g_joystickButtonMask = 0;
	int g_joyButtons[32] = {0};
	float g_joystickAxis[3] = {0};
	float g_joystickAxisLast[3] = {0};
	int g_joystickPollInterval = 16;
	int g_timer1millis = 0;
	int g_timer2millis = 0;
	int g_timer3millis = 0;
	int g_timer4millis = 0;
	int g_timer1value = 0;
	int g_timer2value = 0;
	int g_timer3value = 0;
	int g_timer4value = 0;
	double g_updateInterval = 0.01666667f;
	double g_renderInterval = 0.01666667f;
	double g_afterInitTime = 0;
	double g_elapsedTime = 0;
	double g_Fov = 45.0;
	double g_zNear = 0.01;
	double g_zFar = 100.0;
	int g_modifiers = 0;
	bool g_keyMap[256] = {false};
	bool g_specialKeyMap[256] = {false};
	bool g_shiftKey = false;
	bool g_ctrlKey = false;
	bool g_altKey = false;
	bool g_tabKey = false;


	void display();
	void reshape(int width, int height);
	void keyboard(unsigned char key, int x, int y);
	void keyboardup(unsigned char key, int x, int y);
	void special(int key, int x, int y);
	void specialup(int key, int x, int y);
	void mouse(int button, int state, int x, int y);
	void motion(int x, int y);
	void passive(int x, int y);
	void entry(int state);
	void visibility(int state);
	void idle();
	void timer1(int value);
	void timer2(int value);
	void timer3(int value);
	void timer4(int value);
	void menu(int state);
	void menustatus(int state, int x, int y);
	void windowstatus(int state);
	void joystick(unsigned int buttonMask, int x, int y, int z);
	void Set2DViewport();
	void Set3DViewport();
	bool KeyUp(char c);
	bool KeyDown(char c);
	bool SpecialKeyUp(int key);
	bool SpecialKeyDown(int key);


	void Set2DViewport()
	{
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluOrtho2D(0, g_screenWidth, g_screenHeight, 0);
		glMatrixMode(GL_MODELVIEW);
	}


	void Set3DViewport()
	{
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(g_Fov, g_aspectRatio, g_zNear, g_zFar);
		glMatrixMode(GL_MODELVIEW);
	}


	bool KeyDown(char c)
	{
		int lower = tolower(c);
		int upper = toupper(c);
		if (g_keyMap[lower] || g_keyMap[upper])
			return true;
		return false;
	}


	bool KeyUp(char c)
	{
		int lower = tolower(c);
		int upper = toupper(c);
		if (!g_keyMap[lower] && !g_keyMap[upper])
			return true;
		return false;
	}


	bool SpecialKeyDown(int key)
	{
		if (g_specialKeyMap[key])
			return true;
		return false;
	}


	bool SpecialKeyUp(int key)
	{
		if (!g_specialKeyMap[key])
			return true;
		return false;
	}


	void HandleJoyButtonChange(int mask, int oldMask, int newMask)
	{
		oldMask &= mask;
		newMask &= mask;
		
		int bit = 0;
		
		while (mask)
		{
			if (mask & 1)
				break;
			mask >>= 1;
			bit++;
		}
		
		if (oldMask != newMask)
		{
			if (newMask)
			{
				g_joyButtons[bit] = 1;
				GLUT.OnJoyButtonDown(bit);
			}
			else
			{
				g_joyButtons[bit] = 0;
				GLUT.OnJoyButtonUp(bit);
			}
		}
	}


	void display()
	{
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
		glClearColor(0.0, 0.0, 0.0, 1.0);
		
		glLoadIdentity();
		Set3DViewport();
		GLUT.OnRender();
		
		glLoadIdentity();
		Set2DViewport();
		GLUT.OnRenderOverlay();
		
		if (g_displayMode & GLUT_DOUBLE)
		{
			glutSwapBuffers();
		}
	}

	
	void reshape(int width, int height)
	{
		g_aspectRatio = (double)width / (double)height;
		g_screenWidth = width;
		g_screenHeight = height;
		glViewport(0, 0, width, height);
	}

	
	void keyboard(unsigned char key, int x, int y)
	{
		g_modifiers = glutGetModifiers();
		g_shiftKey = (g_modifiers & GLUT_ACTIVE_SHIFT) != 0;
		g_ctrlKey = (g_modifiers & GLUT_ACTIVE_CTRL) != 0;
		g_altKey = (g_modifiers & GLUT_ACTIVE_ALT) != 0;
		
		g_tabKey = false;		
		if (key == 9 || key == 25 || key == '\t')
		{
			g_tabKey = true;
		}
		
		if (g_tabKey == true)
			key = '\t';
		g_keyMap[key] = true;
		GLUT.OnKeyDown(key);		
	}


	void keyboardup(unsigned char key, int x, int y)
	{
		g_modifiers = glutGetModifiers();
		g_shiftKey = (g_modifiers & GLUT_ACTIVE_SHIFT) != 0;
		g_ctrlKey = (g_modifiers & GLUT_ACTIVE_CTRL) != 0;
		g_altKey = (g_modifiers & GLUT_ACTIVE_ALT) != 0;
		
		g_tabKey = false;		
		if (key == 9 || key == 25 || key == '\t')
		{
			g_tabKey = true;
		}
		
		if (g_tabKey == true)
			key = '\t';
		g_keyMap[key] = false;
		GLUT.OnKeyUp(key);
	}

	
	void special(int key, int x, int y)
	{
		g_modifiers = glutGetModifiers();
		g_specialKeyMap[key] = true;
		g_shiftKey = (g_modifiers & GLUT_ACTIVE_SHIFT) != 0;
		g_ctrlKey = (g_modifiers & GLUT_ACTIVE_CTRL) != 0;
		g_altKey = (g_modifiers & GLUT_ACTIVE_ALT) != 0;
		GLUT.OnSpecialKeyDown(key);
	}

	
	void specialup(int key, int x, int y)
	{
		g_modifiers = glutGetModifiers();
		g_specialKeyMap[key] = false;
		GLUT.OnSpecialKeyUp(key);
	}

	
	void mouse(int button, int state, int x, int y)
	{
		int buttonMask = 1 << button;
		if (state == GLUT_UP)
		{
			g_mouseButtons &= ~buttonMask;
			GLUT.OnMouseButtonUp(button);
		}
		else
		{
			g_mouseButtons |= buttonMask;
			GLUT.OnMouseButtonDown(button);
		}
		g_mouseLastX = g_mouseX;
		g_mouseLastY = g_mouseY;
		g_mouseX = x;
		g_mouseY = y;
		GLUT.OnMouseMotion(g_mouseX, g_mouseY, g_mouseX - g_mouseLastX, g_mouseY - g_mouseLastY, g_mouseButtons);
	}

	
	void motion(int x, int y)
	{
		g_mouseLastX = g_mouseX;
		g_mouseLastY = g_mouseY;
		g_mouseX = x;
		g_mouseY = y;
		GLUT.OnMouseMotion(g_mouseX, g_mouseY, g_mouseX - g_mouseLastX, g_mouseY - g_mouseLastY, g_mouseButtons);
	}

	
	void passive(int x, int y)
	{
		g_mouseLastX = g_mouseX;
		g_mouseLastY = g_mouseY;
		g_mouseX = x;
		g_mouseY = y;
		GLUT.OnMouseMotion(g_mouseX, g_mouseY, g_mouseX - g_mouseLastX, g_mouseY - g_mouseLastY, g_mouseButtons);
	}

	
	void entry(int state)
	{
	}

	
	void visibility(int state)
	{
	}

	
	void idle()
	{
		static double time0 = 0, time1 = 0;
		static double renderdt = 0;
		static double updatedt = 0;
		static int fps_count = 0;
		static double fps0 = 0, fps1 = 0;
		
		time0 = time1;
		time1 = glutGet(GLUT_ELAPSED_TIME) / 1000.0;
		renderdt += time1 - time0;
		updatedt += time1 - time0;
		
		g_elapsedTime = time1;
		
		if (fps_count == 20)
		{
			fps0 = fps1;
			fps1 = time1;
			fps_count = 0;
		}
		
		g_Fps = 20.0 / (fps1 - fps0);
		
		if (updatedt > g_updateInterval)
		{
			GLUT.OnUpdate(updatedt);
			updatedt = 0;
		}
		
		if (renderdt > g_renderInterval)
		{
			fps_count++;	
			glutPostRedisplay();
			renderdt = 0;
		}
	}

	
	void timer1(int value)
	{
		GLUT.OnTimer(0, g_timer1value, g_timer1millis / 1000.0);
	}

	
	void timer2(int value)
	{
		GLUT.OnTimer(1, g_timer1value, g_timer1millis / 1000.0);
	}

	
	void timer3(int value)
	{
		GLUT.OnTimer(2, g_timer1value, g_timer1millis / 1000.0);
	}

	
	void timer4(int value)
	{
		GLUT.OnTimer(3, g_timer1value, g_timer1millis / 1000.0);
	}

	
	void menu(int state)
	{
	}

	
	void menustatus(int state, int x, int y)
	{
	}

	
	void windowstatus(int state)
	{
	}

	
	void joystick(unsigned int buttonMask, int x, int y, int z)
	{
		int axes[3] = {x, y, z};
		unsigned int oldMask = g_joystickButtonMask;
		g_joystickButtonMask = buttonMask;
		
		// If XOR Test is not zero, then there has been a change of buttons
		if ((oldMask ^ buttonMask) != 0)
		{
			int mask = 1;
			for (int i = 0; i < 32; i++)
			{
				HandleJoyButtonChange(mask, oldMask, buttonMask);
				mask <<= 1;
			}
		}

		for (int i = 0; i < 3; i++)
		{
			g_joystickAxisLast[i] = g_joystickAxis[i];
			g_joystickAxis[i] = axes[i] / 1000.0;
			if (g_joystickAxisLast[i] != g_joystickAxis[i])
			{
				GLUT.OnJoyAxis(i, g_joystickAxis[i], g_joystickAxis[i] - g_joystickAxisLast[i]);
			}
		}
	}
}


// G l u t T e m p l a t e ///////////////////////////////////////////


GlutTemplate::GlutTemplate()
	: application(nullptr)
{
}


GlutTemplate::~GlutTemplate()
{
}


void GlutTemplate::Init(int argc, char **argv)
{
	glutInit(&argc, argv);
}


void GlutTemplate::SetVideoMode(int width, int height, int bpp, bool fullscreen)
{
	GlutTemplatePrivate::g_screenWidth = width;
	GlutTemplatePrivate::g_screenHeight = height;
	glutInitDisplayMode(GlutTemplatePrivate::g_displayMode);
	glutInitWindowSize(width, height);
	glutCreateWindow(GlutTemplatePrivate::g_windowTitle);
}


void GlutTemplate::RegisterViperfish()
{
}


void GlutTemplate::RegisterApplication(Viperfish::Application *app)
{
	application = app;
	if (application) application->OnInit();
}


void GlutTemplate::Start()
{
	using namespace GlutTemplatePrivate;
	glutDisplayFunc(GlutTemplatePrivate::display);
	glutReshapeFunc(GlutTemplatePrivate::reshape);
	glutKeyboardFunc(GlutTemplatePrivate::keyboard);
	glutKeyboardUpFunc(GlutTemplatePrivate::keyboardup);
	glutSpecialFunc(GlutTemplatePrivate::special);
	glutSpecialUpFunc(GlutTemplatePrivate::specialup);
	glutMouseFunc(GlutTemplatePrivate::mouse);
	glutMotionFunc(GlutTemplatePrivate::motion);
	glutPassiveMotionFunc(GlutTemplatePrivate::passive);
	glutEntryFunc(GlutTemplatePrivate::entry);
	glutVisibilityFunc(GlutTemplatePrivate::visibility);
	glutIdleFunc(GlutTemplatePrivate::idle);
	if (g_timer1millis != 0) glutTimerFunc(g_timer1millis, timer1, g_timer1value);
	if (g_timer2millis != 0) glutTimerFunc(g_timer2millis, timer2, g_timer2value);
	if (g_timer3millis != 0) glutTimerFunc(g_timer3millis, timer3, g_timer3value);
	if (g_timer4millis != 0) glutTimerFunc(g_timer4millis, timer4, g_timer4value);
	glutMenuStateFunc(GlutTemplatePrivate::menu);
	glutMenuStatusFunc(GlutTemplatePrivate::menustatus);
	glutWindowStatusFunc(GlutTemplatePrivate::windowstatus);
	glutJoystickFunc(GlutTemplatePrivate::joystick, g_joystickPollInterval);

	if (application)
		application->OnStart();

	//afterInitTime = glutGet(GLUT_ELAPSED_TIME) / 1000.0f;
	//cout << glGetString(GL_EXTENSIONS) << endl;
	//cout << glGetString(GL_VERSION) << endl;
	glutMainLoop();
}


void GlutTemplate::OnInit()
{
	if (application) application->OnInit();
}


void GlutTemplate::OnKill()
{
	if (application) application->OnKill();
}


void GlutTemplate::OnUpdate(double deltaTime)
{
	if (application) application->OnUpdate(deltaTime);
}


void GlutTemplate::OnTimer(int timerId, int value, double timerInterval)
{
	if (application) application->OnTimer(timerId, value, timerInterval);
}


void GlutTemplate::OnRender()
{
	if (application) application->OnRender();
}


void GlutTemplate::OnRenderOverlay()
{
	if (application) application->OnRenderOverlay();
}


void GlutTemplate::OnStart()
{
	if (application) application->OnStart();
}


void GlutTemplate::OnStop()
{
	if (application) application->OnStop();
}


void GlutTemplate::OnPause()
{
	if (application) application->OnPause();
}


void GlutTemplate::OnResume()
{
	if (application) application->OnResume();
}


void GlutTemplate::OnKeyUp(int key)
{
	if (application) application->OnKeyUp(key);
}


void GlutTemplate::OnKeyDown(int key)
{
	if (application) application->OnKeyDown(key);
	else if (key == 27)
		exit(0);
}

void GlutTemplate::OnSpecialKeyDown(int key)
{
	if (application) application->OnSpecialKeyDown(key);
}


void GlutTemplate::OnSpecialKeyUp(int key)
{
	if (application) application->OnSpecialKeyUp(key);
}


void GlutTemplate::OnMouseButtonDown(int button)
{
	if (application) application->OnMouseButtonDown(button);
}


void GlutTemplate::OnMouseButtonUp(int button)
{
	if (application) application->OnMouseButtonUp(button);
}


void GlutTemplate::OnMouseMotion(int x, int y, int xrel, int yrel, int buttons)
{
	if (application) application->OnMouseMotion(x, y, xrel, yrel, buttons);
}


void GlutTemplate::OnJoyAxis(int axis, float value, float delta)
{
	if (application) application->OnJoyAxis(axis, value, delta);
}


void GlutTemplate::OnJoyButtonDown(int button)
{
	if (application) application->OnJoyButtonDown(button);
}


void GlutTemplate::OnJoyButtonUp(int button)
{
	if (application) application->OnJoyButtonUp(button);
}
